package com.synjones.wschool.face.serviceImpl;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Table;
import com.synjones.wschool.common.exception.BizException;
import com.synjones.wschool.common.util.DateUtil;
import com.synjones.wschool.face.constant.ModuleSourceEnum;
import com.synjones.wschool.face.constant.UserSourceEnum;
import com.synjones.wschool.face.domain.*;
import com.synjones.wschool.face.mapper.FaceInfoMapper;
import com.synjones.wschool.face.service.*;
import com.synjones.wschool.face.service.request.AllowMealReq;
import com.synjones.wschool.face.service.request.FaceCheckReq;
import com.synjones.wschool.face.service.request.FeedBackReqReq;
import com.synjones.wschool.face.service.response.AllowMealResp;
import com.synjones.wschool.face.service.response.FaceInfoMultiCheckResp;
import com.synjones.wschool.onecard.api.common.ResultResp;
import com.synjones.wschool.onecard.api.req.ApiGetUserInfoReq;
import com.synjones.wschool.onecard.api.resp.ApiGetUserInfoResp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by 24398 on 2018/7/27.
 */
@Service
@Slf4j
public class FaceInfoServiceImpl implements FaceInfoService {
    @Resource
    private FaceInfoMapper faceInfoMapper;
    @Resource
    private TeacherService teacherService;
    @Resource
    private FileStorageService fileStorageService;
    @Resource
    private OrderMealItemService orderMealItemService;
    @Resource
    private OrderMealService orderMealService;
    @Resource
    private OneCardService oneCardService;

    @Value("${file.image.url.template}")
    private String FILE_PREFIX;

    private static final String DATETIME_FORMAT_DEFAULT = "yyyy-MM-dd HH:mm:ss";
    private static final String DATETIME_FORMAT_SHORT = "yyyy-MM-dd HH:mm";
    private static final Integer ORDER_SUCCESS = 1;
    private static final Integer ORDER_FAIL = 0;


    @Override
    public List<FaceInfoMultiCheckResp> listUnCheckedFaceInfo(FaceCheckReq checkReq) {
        List<FaceInfo> faceInfos = this.faceInfoMapper.listFaceInfoByState(FaceInfoStateEnum.CHECKING.getStateValue());
        if(faceInfos == null || faceInfos.size() == 0){
            return Collections.emptyList();
        }
        // 查询学工号
        List<Long>  faceInfoUserIds = faceInfos.parallelStream().map(FaceInfo::getTeacherId).collect(Collectors.toList());
        List<Teacher> teachers = this.teacherService.listTeacherByIds(faceInfoUserIds);
        if(teachers.isEmpty()){
            log.error("查询未审核人脸信息失败：[{}]","没有查询到教师信息");
            throw new BizException("没有查询到教师信息");
        }
        Map<Long,String> teacherIdEnoMap = teachers.parallelStream().collect(Collectors.toMap(Teacher::getSid,Teacher::getEno));
        // 查询图片
        List<Long> faceInfoIds = faceInfos.parallelStream().map(FaceInfo::getId).collect(Collectors.toList());
        List<FileStorage> fileStorages = this.fileStorageService.selectList(faceInfoIds, ModuleSourceEnum.FACE_INFO.getSourceId());
        if(fileStorages.size() != faceInfoIds.size()){
            log.error("查询未审核人脸信息失败： [{}]","人脸图片数量与人脸记录不相等");
            throw new BizException("人脸图片数量与人脸记录不想等");
        }
        ListMultimap<Long,String> sourceIdUrlMultiMap = ArrayListMultimap.create();
        fileStorages.forEach(fileStorage -> {
            sourceIdUrlMultiMap.put(fileStorage.getFileSourceId(),String.format(FILE_PREFIX,fileStorage.getUrl()));
        });

        // 转换
        List<FaceInfoMultiCheckResp> faceInfoMultiCheckResps = faceInfos.parallelStream().map(faceInfo -> FaceInfoMultiCheckResp.builder()
                .userName(faceInfo.getTeacherName())
                .createTime(DateUtil.date2Str(faceInfo.getGmtCreate(),DATETIME_FORMAT_DEFAULT))
                .userNumber(teacherIdEnoMap.get(faceInfo.getTeacherId()))
                .imageUrl(sourceIdUrlMultiMap.get(faceInfo.getId()).get(0))
                .build())
                .collect(Collectors.toList());

        return faceInfoMultiCheckResps;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void faceInfoCheckResult(FeedBackReqReq faceInfoFeedBackReq) {
        // 检测用户的学工号
        List<String> checkUserEnos = faceInfoFeedBackReq.getDetectionInfos().parallelStream()
                                                    .map(FeedBackReqReq.DetectionInfo::getUserNumber)
                                                    .collect(Collectors.toList());
        if(checkUserEnos == null || checkUserEnos.isEmpty()){
            return;
        }

        // 查询检测用户 目前是教师
        List<Teacher> teachers = this.teacherService.listTeacherByEno(checkUserEnos);
        if(teachers.isEmpty()){
            log.error("人脸认证回调失败: [{}]","没有查询到认证用户");
            throw new BizException("没有查询到认证用户");
        }
        Map<String,Long> teacherEnoIdMap = teachers.parallelStream().collect(Collectors.toMap(Teacher::getEno,Teacher::getSid));
        List<Long> teacherIds = teachers.parallelStream().map(Teacher::getSid).collect(Collectors.toList());
        List<FaceInfo> faceInfoList = this.faceInfoMapper.listFaceInfo(teacherIds,UserSourceEnum.TEACHER.getUserSourceId());
        Table<Long,Integer,Long> table =   HashBasedTable.create();
        faceInfoList.forEach(faceInfo -> {
            table.put(faceInfo.getTeacherId(),faceInfo.getUserSourceId(),faceInfo.getId());
        });

        // 转换
        Date now = new Date();
        List<FaceInfo> faceInfos = faceInfoFeedBackReq.getDetectionInfos().parallelStream()
                .map(detectionInfo -> FaceInfo.builder().userSourceId(UserSourceEnum.TEACHER.getUserSourceId())
                        .id(table.get(teacherEnoIdMap.get(detectionInfo.getUserNumber()),UserSourceEnum.TEACHER.getUserSourceId()))
                        .auditState(detectionInfo.getQualified() == true ? 1 : 2)
                        .auditFailReason(detectionInfo.getReason())
                        .gmtModified(now)
                        .build()
                ).collect(Collectors.toList());

        // 批量修改
        if(faceInfos.size() > 0){
            this.faceInfoMapper.batchUpdate(faceInfos);
        }
    }



    @Override
    public AllowMealResp allowMeal(AllowMealReq req) {
        // 查詢一卡通信息
        ApiGetUserInfoReq apiGetUserInfoReq = new ApiGetUserInfoReq();
        apiGetUserInfoReq.setSno(req.getUserNumber());
        ResultResp<ApiGetUserInfoResp> respResultResp =  oneCardService.getUserInfo(apiGetUserInfoReq);
        ApiGetUserInfoResp apiGetUserInfoResp = null;
        if(respResultResp.isResult()){
            apiGetUserInfoResp = respResultResp.getData();
        }else{
            log.error("查询是否允许就餐出错： [{}]","查询一卡通信息出错");
            throw new BizException("查询一卡通信息出错");
        }
        String cardNumber = apiGetUserInfoResp.getCardNo();
        BigDecimal account = apiGetUserInfoResp.getBalance();

        // 根据eno号查詢订餐人员  目前是教师
        Teacher teacher = this.teacherService.fetchByEno(req.getUserNumber());
        if(teacher == null){
            log.error("查询是否允许就餐出错： [{}]","没有查询到教师信息");
            throw new BizException("没有查询到教师信息");
        }
        Long orderUserId = teacher.getSid();
        Integer orderUserSource = UserSourceEnum.TEACHER.getUserSourceId();
        boolean orderSuccess = true;
        // 查詢该教师当天预定就餐情况，不管成功失败
        Date beginTime = DateUtil.getDate();  // 当天开始时间
        Date endTime = DateUtil.getDate(beginTime,24, ChronoUnit.HOURS);
        List<OrderMealItem> dayOrderMealItems = this.orderMealItemService.selectList(beginTime,endTime,orderUserId,orderUserSource);

        if(dayOrderMealItems == null || dayOrderMealItems.isEmpty()){
          AllowMealResp allowMealResp= AllowMealResp.builder().allowMeal(false)
                                           .reason("该用户没有订餐")
                                           .cardNumber(cardNumber)
                                           .amount(account.doubleValue())
                                           .build();

          return allowMealResp;
        }
        List<Long> orderMealIds = dayOrderMealItems.parallelStream().map(OrderMealItem::getOrderMealId).collect(Collectors.toList());
        List<OrderMeal> orderMeals = this.orderMealService.listByIds(orderMealIds);
        boolean hasOrder = orderMeals.parallelStream().anyMatch(orderMeal -> orderMeal.getMealType().equals(req.getMealType()));

        AllowMealResp allowMealResp= AllowMealResp.builder()
                .allowMeal(this.judgeOrderSuccess(req.getMealType(),dayOrderMealItems,orderMeals))
                .reason(this.getReason(req.getMealType(),dayOrderMealItems,orderMeals))
                .cardNumber(cardNumber)
                .amount(account.doubleValue())
                .build();

        return allowMealResp;
    }

    private boolean judgeOrderSuccess(Integer mealType,List<OrderMealItem> dayOrderMealItems,List<OrderMeal> orderMeals){
        boolean hasOrder = orderMeals.parallelStream().anyMatch(orderMeal -> orderMeal.getMealType().equals(mealType));
        if(!hasOrder){
            return false;
        }
       OrderMeal matchOrderMeal = orderMeals.parallelStream().filter(orderMeal -> orderMeal.getMealType().equals(mealType))
                                       .findFirst().get();

       Long orderMealId = matchOrderMeal.getId();
       OrderMealItem matchOrderMealItem = dayOrderMealItems.parallelStream().filter(orderMealItem -> orderMealItem.getOrderMealId().equals(orderMealId))
                                                                       .findFirst().get();


        return matchOrderMealItem.getOrderSuccess() == ORDER_SUCCESS ? true :false;
    }


    private String getReason(Integer mealType,List<OrderMealItem> dayOrderMealItems,List<OrderMeal> orderMeals){
        boolean hasOrder = orderMeals.parallelStream().anyMatch(orderMeal -> orderMeal.getMealType().equals(mealType));
        if(!hasOrder){
            return "该用户没有预定";
        }
        OrderMeal matchOrderMeal = orderMeals.parallelStream().filter(orderMeal -> orderMeal.getMealType().equals(mealType))
                .findFirst().get();

        Long orderMealId = matchOrderMeal.getId();
        OrderMealItem matchOrderMealItem = dayOrderMealItems.parallelStream().filter(orderMealItem -> orderMealItem.getOrderMealId().equals(orderMealId))
                                               .findFirst().get();

        if(matchOrderMealItem.getOrderSuccess().equals(ORDER_FAIL)){
            return matchOrderMealItem.getDeductMsg();
        }

        return "";
    }

    @Override
    public List<FaceInfo> listCheckedFaceInfo(Date afterDate,List<Integer> userSourceIds ,boolean successChecked) {
        Byte state = null;
        if(successChecked){
            state = FaceInfoStateEnum.CHECK_SUCCESS.getStateValue().byteValue();
        }else{
            state = FaceInfoStateEnum.CHECK_FAULT.getStateValue().byteValue();
        }
        List<FaceInfo> faceInfos = this.faceInfoMapper.listFaceInfoByDateAndState(afterDate,userSourceIds,state);
        return faceInfos;
    }

    public enum FaceInfoStateEnum{
        CHECKING(0,"审核中"),
        CHECK_SUCCESS(1,"审核成功"),
        CHECK_FAULT(2,"审核失败");
        private Integer stateValue;
        private String  stateDesc;
        FaceInfoStateEnum(Integer stateValue,String stateDesc){
            this.stateValue = stateValue;
            this.stateDesc = stateDesc;
        }
        public Integer getStateValue(){return  this.stateValue;}
        public String getStateDesc(){return  this.stateDesc;}
    }



    public enum MealTypeEnum{
        BREAKFAST(1,"早餐"),
        LUNCH(2,"午餐"),
        SUPPER(3,"晚餐");
        private Integer typeValue;
        private String typeDesc;

        MealTypeEnum(Integer typeValue,String typeDesc){
            this.typeValue = typeValue;
            this.typeDesc = typeDesc;
        }

        public Integer getTypeValue(){return  this.typeValue;}
        public String getTypeDesc(){return this.typeDesc;}

        public static String getDesc(Integer typeValue){

            MealTypeEnum[] enums = MealTypeEnum.values();
            for(int i = 0;i< enums.length;i++){
                if(enums[i].typeValue.equals(typeValue)){
                    return enums[i].typeDesc;
                }
            }
            return "";
        }
    }
}
